#include <cstdio>
#include <cstdint>
#include <cstdlib>
#include <signal.h>
#include <unistd.h>
#include <linux/futex.h>
#include <linux/sched.h>
#include <sys/syscall.h>

static void millisleep(int ms) {
    long ts[] = {ms / 1000, ms % 1000 * 1000000L};
    syscall(SYS_nanosleep, ts, ts);
}

/* int num = 65; */ // Ignore this, used for my own personal test

static void futex_wait(int* futex, int expect) {
    syscall(SYS_futex, futex, FUTEX_WAIT, expect, 0);
}

static void futex_wake(int* futex) {
    syscall(SYS_futex, futex, FUTEX_WAKE, 0x7fffffff);
}

/* The structure must have a 16-byte alignment on all architectures */
struct __attribute((aligned(16))) stack_head {
    void (*entry)(struct stack_head*); // The entry point pointer. Will receive a pointer to its own stack_head

    /* The rest of the arguments can be filled with any thread-local
       data we want. The following is just an example! */
    //const char* message;
    //int print_count;
    int join_futex; // Join "futex" to help us "wait" to "join" that thread later
};

struct thread_stack {
    void* tail;
    stack_head* head;
};

[[__gnu__::__naked__]]
static long newthread(clone_args* args, ::std::size_t clone_args_size) {
    __asm volatile(
        /* "args" is already in "rdi" */
        "mov  $435, %%eax\n" // SYS_clone3
        "syscall\n"
        "mov  %%rsp, %%rdi\n" // entry point argument
        "test %%rax, %%rax\n"
        "js .Ltrap\n"
        "ret\n"
        ".Ltrap:"
        "ud2"
        :
        :
        : "rax", "rcx", "rsi", "rdi", "r11", "memory");
}

[[noreturn]]
static void threadentry(stack_head* stack) {
    printf("Child thread alive!!! Printing messages.\n");
//    const char* message = stack->message;
//    int count = stack->print_count;
//    for (int i = 0; i < count; i++) {
//        printf("%s", message);
//        millisleep(25);
//    }

    ::std::printf("child tid: %d\n", gettid());
    millisleep(1000);

    __atomic_store_n(&stack->join_futex, 1, __ATOMIC_SEQ_CST);
    futex_wake(&stack->join_futex);
    syscall(SYS_exit, 0);
    __builtin_unreachable();
}

static thread_stack newstack(::std::size_t size) {
    void* p = malloc(size);

    if (p == NULL) { /* Return "null" on failure */
        __builtin_trap();
    }

    long count = size / sizeof(stack_head);
    thread_stack stack = {p, (stack_head*)p + count - 1};
    return stack;
}

int main() {
    thread_stack stack = newstack(1 << 16);
    stack.head->entry = threadentry;

    ::std::printf("father tid: %d\n", gettid());

    // Thread data
    //stack.head->message = "hello world\n";
    //stack.head->print_count = 20;

    stack.head->join_futex = 0; // Set the "futex"

    clone_args args = {CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD,
                      0,
                      0,
                      0,
                      0,
                      (__aligned_u64)stack.tail, /* stack */
                      (uintptr_t)stack.head - (uintptr_t)stack.tail, /* stack_size */
                      0,
                      0,
                      0,
                      0};

    long ret = newthread(&args, sizeof(clone_args));

    futex_wait(&stack.head->join_futex, 0);
    printf("Ret for clone3(): %ld\n", ret);
    printf("Parent exited\n");

    return 0;
}
